Skip to content

feat: Color pairing tool#524

Draft
nicoledbelcher wants to merge 9 commits intocoinbase:masterfrom
nicoledbelcher:nicole/colorpicker
Draft

feat: Color pairing tool#524
nicoledbelcher wants to merge 9 commits intocoinbase:masterfrom
nicoledbelcher:nicole/colorpicker

Conversation

@nicoledbelcher
Copy link
Copy Markdown

@nicoledbelcher nicoledbelcher commented Mar 20, 2026

What changed? Why?

Added a new sidebar to the docs site called extras and created a new color pairing tool inside it.
What it is
The Color Pairing Tool is an internal design utility built into the CDS documentation site. It helps designers and engineers find the closest CDS spectrum primitives for any color and automatically generates accessible, theme-aware color pairings that work across light and dark modes.

How it works

  1. Color Input (two methods)

Image upload — Users can upload up to 10 images (PNG, JPG, or WebP) via file picker or drag-and-drop. The tool extracts dominant colors from each image using a k-means clustering algorithm run client-side on an .
Manual hex entry — Users can type one or more comma-separated hex codes directly into a text input.
2. Token Matching

For each extracted or entered color, the tool runs a hue-aware matching algorithm against the full CDS spectrum (both light and dark). This isn't a simple "nearest Euclidean distance" match — it's purpose-built for CDS tokens and factors in perceptual hue so the matched primitive feels like a natural fit, not just the mathematically closest one.

  1. Contrast & Accessibility

Once a primary background token is matched, the tool:

Selects a foreground text color that meets WCAG AA contrast requirements (minimum 4.5:1 for normal text).
Computes a secondary/complementary token pairing.
Offers a high-contrast mode toggle that enforces even stricter contrast ratios.
Displays live WCAG contrast ratios (AA Normal, AA Large, AAA) for every pairing.
4. Component Preview

The results feed into a Component Playground that renders real CDS components — Card, Button, MessagingCard, LineChart, ProgressBar, and more — using the matched token pairings. This gives an immediate preview of how the colors will look in a production UI across both light and dark themes.

  1. Resampling

For image uploads, users can drag a hotspot around the image to resample the dominant color from a different region, which recalculates the token match and all downstream pairings in real time.

  1. Export

Results can be exported as structured JSON containing the matched tokens, hex values, text colors, and button state variants for both light and dark modes — ready to hand off to engineering.

Key technical details
Runs entirely client-side — no server calls. All color math, image processing, and token matching happen in the browser.
Uses CDS design tokens as the source of truth (imported from tokens.ts), so matches always stay in sync with the design system.
Theme-aware throughout — every visual (checkerboards, previews, component playground) adapts to the user's current light/dark mode setting.

UI changes

Screenshot 2026-03-27 at 9 48 52 AM Screenshot 2026-03-27 at 9 49 28 AM Screenshot 2026-03-27 at 9 49 19 AM

Multiple outputs carousel:
Screenshot 2026-03-27 at 11 14 15 AM

Testing

You should test selecting a color from the color picker without uploading an image, and then test uploading one or multiple images. Users can only upload, PNG, JGP and WEBP files, no video/animated gif files allowed.

How has it been tested?

  • Unit tests
  • Interaction tests
  • Pseudo State tests
  • Manual - Web
  • Manual - Android (Emulator / Device)
  • Manual - iOS (Emulator / Device)

Testing instructions

Illustrations/Icons Checklist

Required if this PR changes files under packages/illustrations/** or packages/icons/**

  • verified visreg changes with Terran (include link to visreg run/approval)
  • all illustration/icons names have been reviewed by Dom and/or Terran

Change management

type=routine
risk=low
impact=sev5

automerge=false

@cb-heimdall
Copy link
Copy Markdown
Collaborator

cb-heimdall commented Mar 20, 2026

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 1
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1
CODEOWNERS 🟡 See below

🟡 CODEOWNERS

Code Owner Status Calculation
ui-systems-eng-team 🟡 0/1
Denominator calculation
Additional CODEOWNERS Requirement
Show calculation
Sum 0
0
From CODEOWNERS 1
Sum 1

Copy link
Copy Markdown
Contributor

@hcopp hcopp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love the idea! Hopefully this can help you talk with Cursor about how to clean things up.

Also if you could get sample screenshots, description of your intended changes, and a link to the figma in the PR description that would be very helpful!

CDS layout components use inline styles generated from their style props,
which have higher specificity than CSS class rules. !important is required
here to override those inline styles at mobile breakpoints.
────────────────────────────────────────────────────────────────────────── */
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need to use style props, but instead use our regular component props which support responsive values.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cursor:

Here's a comment you can post:

Acknowledged — the !important overrides in ResultCard.module.css exist because CDS layout components (HStack, VStack, Box) apply style props as inline styles, which have higher specificity than CSS class rules. The fix is to replace these with responsive CDS component props (e.g. flexDirection={{ base: 'column', tablet: 'row' }}, width={{ base: '100%', tablet: '50%' }}).

We've already done this for ColorPicker.module.css (removed the .pickerRow !important override in this round). The remaining overrides in ResultCard.module.css touch layout across ResultCard, ComponentPlayground, and ContrastPanel simultaneously and need visual QA at both desktop and mobile breakpoints. Deferring to a dedicated follow-up PR.

@hcopp
Copy link
Copy Markdown
Contributor

hcopp commented Mar 27, 2026

@nicoledbelcher are you able to fill out the PR description? This is done in GitHub UI manually. You can look at other PRs to see what we normally do to fill it out.

image

Also your commits are unverified, you should follow https://docs.github.com/en/authentication/managing-commit-signature-verification/adding-a-gpg-key-to-your-github-account. This is a requirement to merge in code.

Lastly, can you pull in the latest from master? Cursor can help with this.

@nicoledbelcher nicoledbelcher changed the title Nicole/colorpicker feat: Color pairing tool Mar 27, 2026
nicoledbelcher and others added 9 commits March 27, 2026 13:18
Adds ColorPairingTool to docs site, color-pairing utilities and components to vite-app, cds-finder app, guidelines documentation, and various supporting updates.

Made-with: Cursor
…heming

- Remove Color matching / Color pairs tab strip; single-flow layout
- Scope LineChart scrubber overlay/line CSS vars to chart card for light playground
- Hue-aware primitive matching and related adjustments

Made-with: Cursor
…ixes

- Checkerboard thumbnails in UploadZone now respect light/dark color mode
- Light mode thumb borders use subtle dark borders instead of invisible white
- Restore top padding on contrast panel for proper spacing on mobile
- Increase mobile image preview height to 300px for balanced blur padding
- Fix hotspot drifting off image on mobile by removing align-items stretch
- Fix cards row clipping LineChart on mobile by overriding fixed height
- Strip trailing commas in color input instead of creating phantom results
- Remove red error outline when typing a trailing comma
- Remove token tag next to "Color match to components" heading

Made-with: Cursor
- Move reducer/initialState into parent component (index.tsx), rename
  state.ts to types.ts for shared type definitions only
- Remove sharedStyles.ts (CoinbaseMono font not available in docs)
- Move hotspot label inline styles to ResultCard.module.css
- Use CDS Box props instead of inline styles in ContrastPanel
- Replace checker-placeholder.png with theme-aware inline SVG
- Replace banner PNGs with SVGs for light/dark modes
- Simplify LineChart height prop (remove unnecessary responsive object)
- Hide MetadataLinks on pages with no metadata (e.g. playground)
- Run docs:lint --fix (all files pass)

Note: the style props → responsive CDS props refactor (removing
!important overrides in ResultCard.module.css) is deferred to a
follow-up. It touches the responsive layout across multiple components
and needs careful visual QA at both desktop and mobile breakpoints.

Made-with: Cursor
…mprove playground

- Extract PlaygroundContent into its own file for cleaner separation
- Extract generic FileDropZone component and useFileUpload hook for reuse
- Replace hardcoded spectrum values and types in tokens.ts with CDS theme imports
- Remove unused CSS modules and useImageUpload hook
- Simplify ContrastPanel and WcagBadge to use CDS spacing props
- Update LineChart card background to use "bg" token
- Add hideLlmLink option to MetadataLinks component
- Fix ContentHeader bannerHeight JSDoc format

Made-with: Cursor

export type TokenFamily = ThemeVars.SpectrumHue;
export type TokenStep = ThemeVars.SpectrumHueStep;
export type ColorToken = ThemeVars.SpectrumColor;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we shouldn't need to re-export TokenFamily, TokenStep, ColorToken, lightSpectrum, and darkSpectrum. Files importing from here should instead import from useTheme(), or, if unable, import from defaultTheme.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

4 participants